//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Utils/Base/Core.h"

namespace Engine
{
namespace Utils
{

	//
	// Builder Config Parser
	//

	class BuilderConfig
	{
	// variables
	private:
		Ptr< UtilsCore >	_core;


	// metadata
	public:
		struct ProjectBuilder
		{
			string			inputDir;
			string			outputDir;
			string			projConfig;
			string_array_t	directories;
			string_array_t	extensions;
			string_array_t	sourceTypes;
		};
		

		static CMetaClass &		GetMetaClass ()		{ static CMetaClass metaClass;  return metaClass; }
		
		friend UX_CREATE_META_OBJECT( ProjectBuilder,	GetMetaClass() );


		static void InitMetaData ()
		{
			CMetaClass::s_InitBaseTypes();

			CMetaAddIteratorPtr	iter;

			if ( not GetMetaClass().IsInit() )
			{
				ProjectBuilder obj;

				GetMetaClass().CreateExt( "ProjectBuilder", obj );
				iter = GetMetaClass().GetAddIter( obj );

				iter->AddField( obj.inputDir,		"input_dir"   );
				iter->AddField( obj.outputDir,		"output_dir"  );
				iter->AddField( obj.projConfig,		"proj_config" );
				iter->AddField( obj.directories,	"directories" );
				iter->AddField( obj.extensions,		"extensions"  );
				iter->AddField( obj.sourceTypes,	"source_types" );
			}
		}


	// methods
	public:
		BuilderConfig (UtilsCore *core) : _core(core)
		{
			InitMetaData();
		}


		bool LoadConfig (uni_c_string filename)
		{
			static_obj_tree_t	cfg_tree;
			ProjectBuilder		obj;

			cfg_tree.Push( TConfigObjInfo( GetMetaClass().GetSetIter( obj ), null ) );

			CHECK_ERR( _core->Parse( filename, cfg_tree ) );

			return OnLoad( obj );
		}


		bool OnLoad (const ProjectBuilder &obj)
		{
			// check
			CHECK_ERR( FileSystem::IsDirectoryExist( obj.inputDir.cstr() ) );
			
			if ( not FileSystem::IsDirectoryExist( obj.outputDir.cstr() ) )
			{
				FileSystem::NewDirectory( obj.outputDir.cstr() );
			}


			// copy files
			string_array_t	files;
			string			str_from,
							str_to;

			CHECK_ERR( FileSystem::GetAllFilesInPath( obj.inputDir.cstr(), files ) );

			FOR( i, files )
			{
				FileAddressUtils::BuildPath( str_from, obj.inputDir,  files[i] );
				FileAddressUtils::BuildPath( str_to, obj.outputDir, files[i] );

				CHECK_ERR( FileSystem::CopyFile( str_from.cstr(), str_to.cstr() ) );
			}

			// copy directories
			FOR( i, obj.directories )
			{
				FileAddressUtils::BuildPath( str_from, obj.inputDir, obj.directories[i] );

				CHECK_ERR( FileSystem::IsDirectoryExist( str_from.cstr() ) );
				CHECK_ERR( FileSystem::CopyDirectory( str_from.cstr(), obj.outputDir.cstr() ) );
			}


			// change Java source
			struct FileFilter
			{
				const ProjectBuilder *	bldr;
				///
				FileFilter (const ProjectBuilder * bldr) : bldr(bldr) {}

				bool operator () (uni_c_string fname)
				{
					return BuilderConfig::_FileFilter( fname, *bldr );
				}
			};


			files.ClearMem();
			CHECK_ERR( FileSystem::RecursiveFindFiles( obj.outputDir.cstr(), FileFilter( &obj ), files ) );

			FOR( i, files )
			{
				FileAddressUtils::BuildPath( str_from, obj.outputDir, files[i] );
				
				CHECK_ERR( _EnableExtensionsInFile( str_from, obj.extensions ) );
			}

			return true;
		}


	private:
		static bool _FileFilter (uni_c_string fname, const ProjectBuilder &cfg)
		{
			usize	pos = 0;

			FOR( i, cfg.sourceTypes )
			{
				if ( fname.FindI( cfg.sourceTypes[i], pos, 0 ) )
					return true;
			}

			return true;
		}


		bool _EnableExtensionsInFile (uni_c_string filename, const string_array_t &extensions) const
		{
			RFilePtr	rfile;
			CHECK_ERR( (rfile = File::HddRFile::New( filename )).IsNotNull() );

			string	buf;
			usize	len = rfile->Size();

			CHECK_ALLOC( buf.Reserve( len + 1 ) );

			CHECK( rfile->Read( buf.ptr(), len ) );
			buf.SetLength( len );

			rfile = null;

			CHECK_ERR( _EnableExtensions( buf, extensions ) );
			
			WFilePtr	wfile;
			CHECK_ERR( (wfile = File::HddWFile::New( filename )).IsNotNull() );

			CHECK( wfile->Write( buf.cstr(), buf.Length() ) );

			if ( len != buf.Length() ) {
				LOG( (string("File \"") << filename << "\", was changed for enable/disable extensions.").cstr(), e_log_msg::DEBUG );
			}
			return true;
		}


		static bool _EnableExtensions (INOUT string &buf, const string_array_t &extensions)
		{
			usize	pos = 0;

			bool_array_t	enabled_ext;

			enabled_ext.Reserve( 10 );
			enabled_ext.Resize( extensions.Count(), false );

			if ( not enabled_ext.Empty() ) {
				ZeroMem( enabled_ext.ptr(), enabled_ext.Count() );
			}

			const static_string<10>		s_ifdef( "#ifdef" );
			const static_string<10>		s_else(  "#else"  );
			const static_string<10>		s_endif( "#endif" );

			while ( true )
			{
				uni_c_string	ext;
				uvec3			block;

				if ( _ReadIfDefBlock( buf, pos, ext, block ) )
				{
					// enable extension?
					string *	result = null;
					bool		enable = extensions.Empty() ? false : FindFirst( extensions, ext, result );
					
					if ( not enable )
					{
						uvec2	limits( block.x, block.y == 0 ? block.z : block.y );
						_ToNextLine( buf, limits.y );
						
						// erase #endif
						if ( block.y != 0 ) {
							_EraseLine( buf, block.z );
						}

						// erase from #ifdef to #else or #endif
						buf.Erase( limits.x, limits.y - limits.x );
					}
					else
					{
						// erase from #else to #endif
						if ( block.y != 0 )
						{
							_ToNextLine( buf, block.z );

							buf.Erase( block.y, block.z - block.y );
						}
						// erase #endif
						else {
							_EraseLine( buf, block.z );
						}
						
						// erase #ifdef
						_EraseLine( buf, block.x );

						enabled_ext[ extensions.GetIndex( result ) ] = enable;
					}

					pos = block.x;
				}
				else
					break;
			}

			/*FOR( i, enabled_ext )
			{
				if ( not enabled_ext[i] )
				{
					LOG( (string("Extension \"") << extensions[i] << "\" not found.").cstr(), e_log_msg::DEBUG );
				}
			}*/

			return true;
		}


		static bool _ReadIfDefBlock (uni_c_string buf, const usize position, OUT uni_c_string &extension,
									 OUT uvec3 &ifdefBlock)
		{
			// ifdefBlock.x - before "#ifdef"
			// ifdefBlock.y - before "#else"
			// ifdefBlock.z - before "#endif"

			const static_string<10>		s_ifdef( "#ifdef" );
			const static_string<10>		s_else(  "#else"  );
			const static_string<10>		s_endif( "#endif" );
			
			ifdefBlock = uvec3();

			usize	pos = position;

			// find #ifdef <extension>
			if ( buf.Find( s_ifdef, pos, pos ) )
			{
				// read extension
				{
					const usize	word_begin = pos + s_ifdef.Length();

					ifdefBlock.x = word_begin;
					_ToPreviouslyLine( buf, ifdefBlock.x );

					ifdefBlock.x++;
					_ToNextLine( buf, pos );

					_ReadWord( buf, word_begin, pos, extension );
				}

				
				// find #endif <extension>
				bool	end_found = false;

				while ( buf.Find( s_endif, pos, pos ) )
				{
					// read extension
					const usize		word_begin = pos + s_endif.Length();
					uni_c_string	ext;

					_ToNextLine( buf, pos );
					_ReadWord( buf, word_begin, pos, ext );

					if ( ext == extension )
					{
						end_found    = true;
						ifdefBlock.z = word_begin;
						_ToLineBegin( buf, ifdefBlock.z );
						break;
					}

					pos++;
				}

				if ( not end_found )
				{
					uni_c_string	line("todo");
					RETURN_ERR( "not found closed macro \"##endif " << extension << "\" for line \"" << line << "\"" );
				}


				// find #else <extension>
				pos = ifdefBlock.x;

				while ( buf.Find( s_else, pos, pos ) )
				{
					// read extension
					const usize		word_begin = pos + s_else.Length();
					uni_c_string	ext;
						
					usize	begin = word_begin;
					_ToPreviouslyLine( buf, begin );

					_ToNextLine( buf, pos );
					_ReadWord( buf, word_begin, pos, ext );

					if ( ext == extension )
					{
						ifdefBlock.y = begin+1;
						break;
					}
				}

				if ( ifdefBlock.y >= ifdefBlock.z ) {
					ifdefBlock.y = 0;
				}

				return true;
			}
			return false;
		}


		static void _EraseLine (INOUT string &buf, const usize pos)
		{
			usize	end = pos;
			_ToNextLine( buf, end );

			buf.Erase( pos, end - pos );
		}


		static void _ToPreviouslyLine (uni_c_string buf, INOUT usize &pos)
		{
			while ( pos != 0 )
			{
				const char	c = buf[pos];
				const char	p = buf[pos-1];

				--pos;

				if ( ( p == 13 or p == 10 ) and
					 ( c != 13 and c != 10 ) )
				{
					return;
				}
			}
		}


		static void _ToNextLine (uni_c_string buf, INOUT usize &pos)
		{
			while ( pos < buf.Length() )
			{
				const char	c = buf[pos];
				const char	n = buf[pos+1];
				
				++pos;

				if ( ( c == 13 or c == 10 ) and
					 ( n != 13 and n != 10 ) )
				{
					return;
				}
			}
		}


		static void _ToLineBegin (uni_c_string buf, INOUT usize &pos)
		{
			while ( pos != 0 )
			{
				const char	c = buf[pos];
				const char	p = buf[pos-1];
				
				if ( p == 13 or p == 10 )
					return;

				--pos;
			}
		}


		static void _ToLineEnd (uni_c_string buf, INOUT usize &pos)
		{
			while ( pos < buf.Length() )
			{
				const char	c = buf[pos];
				const char	n = buf[pos+1];
				
				if ( n == 13 or n == 10 )
					return;

				++pos;
			}
		}


		static void _ReadWord (uni_c_string buf, usize begin, usize end, OUT uni_c_string &word)
		{
			struct Util
			{
				static bool IsWord (const char c)
				{
					return	( c >= 'a' and c <= 'z' ) or
							( c >= 'A' and c <= 'Z' ) or
							( c >= '0' and c <= '9' ) or
							c == '_';
				}
			};

			usize	i			= begin;
			bool	read_word	= false;

			while ( i < end )
			{
				const char	c = buf[i];

				if ( read_word )
				{
					if ( not Util::IsWord( c ) )
					{
						word = uni_c_string( buf.cstr() + begin, i - begin );
						break;
					}
				}
				else
				{
					if ( Util::IsWord( c ) )
					{
						begin		= i;
						read_word	= true;
					}
				}
				++i;
			}

			ASSERT( not word.Empty() );
		}


		static void _GetLine (uni_c_string buf, usize begin, OUT uni_c_string &line)
		{
			usize	end = begin;

			while ( end < buf.Length() )
			{
				const char	c = buf[end];

				if ( c == 13 or c == 10 )
					break;

				++end;
			}

			line = uni_c_string( buf.cstr() + begin, end - begin + 1 );
		}
	};


}	// Utils
}	// Engine
